{
 "cells": [
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# Multi-Horizon Forecasting for Limit Order Books: Novel Deep Learning Approaches and Hardware Acceleration using Intelligent Processing Units\n",
    "### Authors: Zihao Zhang and Stefan Zohren\n",
    "### Oxford-Man Institute of Quantitative Finance, Department of Engineering Science, University of Oxford\n",
    "\n",
    "This jupyter notebook is used to demonstrate our recent paper [2]. We use FI-2010 [1] dataset and present how model architecture is constructed here. The FI-2010 is publicly avilable and interested readers can check out their paper [1]. The dataset can be downloaded from: https://etsin.fairdata.fi/dataset/73eb48d7-4dbc-4a10-a52a-da745b47a649\n",
    "\n",
    "Otherwise, it can be obtained from: https://drive.google.com/drive/folders/1Xen3aRid9ZZhFqJRgEMyETNazk02cNmv?usp=sharing\n",
    "\n",
    "[1] Ntakaris A, Magris M, Kanniainen J, Gabbouj M, Iosifidis A. Benchmark dataset for mid‐price forecasting of limit order book data with machine learning methods. Journal of Forecasting. 2018 Dec;37(8):852-66. https://arxiv.org/abs/1705.03233\n",
    "\n",
    "[2] Zhang Z, Zohren S. Multi-Horizon Forecasting for Limit Order Books: Novel Deep Learning Approaches and Hardware Acceleration using Intelligent Processing Units. https://arxiv.org/abs/2105.10430\n",
    "\n",
    "#### This notebook demonstrates how to train DeepLOB-Seq2Seq by using tensorflow 2 on GPUs."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 1,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "1 Physical GPUs, 1 Logical GPUs\n"
     ]
    }
   ],
   "source": [
    "import tensorflow as tf\n",
    "\n",
    "gpus = tf.config.experimental.list_physical_devices('GPU')\n",
    "if gpus:\n",
    "    try:\n",
    "    # Currently, memory growth needs to be the same across GPUs\n",
    "        for gpu in gpus:\n",
    "            tf.config.experimental.set_memory_growth(gpu, True)\n",
    "            logical_gpus = tf.config.experimental.list_logical_devices('GPU')\n",
    "        print(len(gpus), \"Physical GPUs,\", len(logical_gpus), \"Logical GPUs\")\n",
    "    except RuntimeError as e:\n",
    "    # Memory growth must be set before GPUs have been initialized\n",
    "        print(e)\n",
    "\n",
    "# %%\n",
    "import os\n",
    "import logging\n",
    "import glob\n",
    "import argparse\n",
    "import sys\n",
    "import time\n",
    "import pandas as pd\n",
    "import pickle\n",
    "import numpy as np\n",
    "import matplotlib.pyplot as plt\n",
    "from collections import Counter\n",
    "\n",
    "# set random seeds\n",
    "np.random.seed(1)\n",
    "tf.random.set_seed(2)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 2,
   "metadata": {},
   "outputs": [],
   "source": [
    "from preprocess import *\n",
    "from model_gpu import get_model_seq"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 3,
   "metadata": {},
   "outputs": [],
   "source": [
    "# please change the data_path to your local path\n",
    "data_path = '/nfs/home/zihaoz/limit_order_book/data'\n",
    "T = 50\n",
    "epochs = 50\n",
    "batch_size = 32\n",
    "n_hidden = 64\n",
    "checkpoint_filepath = './model_deeplob_seq/weights'"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 4,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "train_encoder_input.shape = (254701, 50, 40, 1),train_decoder_target.shape = (254701, 5, 3)\n",
      "test_encoder_input.shape = (139538, 50, 40, 1),test_decoder_target.shape = (139538, 5, 3)\n"
     ]
    }
   ],
   "source": [
    "# %%\n",
    "dec_train = np.loadtxt(data_path + '/3.NoAuction_DecPre/NoAuction_DecPre_Training/Train_Dst_NoAuction_DecPre_CF_7.txt')\n",
    "dec_test1 = np.loadtxt(data_path + '/3.NoAuction_DecPre/NoAuction_DecPre_Testing/Test_Dst_NoAuction_DecPre_CF_7.txt')\n",
    "dec_test2 = np.loadtxt(data_path + '/3.NoAuction_DecPre/NoAuction_DecPre_Testing/Test_Dst_NoAuction_DecPre_CF_8.txt')\n",
    "dec_test3 = np.loadtxt(data_path + '/3.NoAuction_DecPre/NoAuction_DecPre_Testing/Test_Dst_NoAuction_DecPre_CF_9.txt')\n",
    "dec_test = np.hstack((dec_test1, dec_test2, dec_test3))\n",
    "\n",
    "# extract limit order book data from the FI-2010 dataset\n",
    "train_lob = prepare_x(dec_train)\n",
    "test_lob = prepare_x(dec_test)\n",
    "\n",
    "# extract label from the FI-2010 dataset\n",
    "train_label = get_label(dec_train)\n",
    "test_label = get_label(dec_test)\n",
    "\n",
    "# prepare training data. We feed past 100 observations into our algorithms.\n",
    "train_encoder_input, train_decoder_target = data_classification(train_lob, train_label, T)\n",
    "train_decoder_input = prepare_decoder_input(train_encoder_input, teacher_forcing=False)\n",
    "\n",
    "test_encoder_input, test_decoder_target = data_classification(test_lob, test_label, T)\n",
    "test_decoder_input = prepare_decoder_input(test_encoder_input, teacher_forcing=False)\n",
    "\n",
    "print(f'train_encoder_input.shape = {train_encoder_input.shape},'\n",
    "      f'train_decoder_target.shape = {train_decoder_target.shape}')\n",
    "print(f'test_encoder_input.shape = {test_encoder_input.shape},'\n",
    "      f'test_decoder_target.shape = {test_decoder_target.shape}')"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 5,
   "metadata": {},
   "outputs": [],
   "source": [
    "model = get_model_seq(n_hidden)\n",
    "model.compile(loss='categorical_crossentropy', metrics=['accuracy'], optimizer='adam')"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "scrolled": true
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Epoch 1/50\n",
      "6368/6368 - 181s - loss: 0.9576 - accuracy: 0.5171 - val_loss: 0.8496 - val_accuracy: 0.6138\n",
      "Epoch 2/50\n",
      "6368/6368 - 179s - loss: 0.6975 - accuracy: 0.7035 - val_loss: 0.7852 - val_accuracy: 0.6524\n",
      "Epoch 3/50\n",
      "6368/6368 - 180s - loss: 0.6446 - accuracy: 0.7311 - val_loss: 0.7602 - val_accuracy: 0.6728\n",
      "Epoch 4/50\n",
      "6368/6368 - 179s - loss: 0.6162 - accuracy: 0.7455 - val_loss: 0.7625 - val_accuracy: 0.6723\n",
      "Epoch 5/50\n",
      "6368/6368 - 180s - loss: 0.5984 - accuracy: 0.7540 - val_loss: 0.7443 - val_accuracy: 0.6825\n",
      "Epoch 6/50\n",
      "6368/6368 - 181s - loss: 0.5848 - accuracy: 0.7603 - val_loss: 0.7305 - val_accuracy: 0.6892\n",
      "Epoch 7/50\n",
      "6368/6368 - 180s - loss: 0.5740 - accuracy: 0.7649 - val_loss: 0.7427 - val_accuracy: 0.6822\n",
      "Epoch 8/50\n",
      "6368/6368 - 180s - loss: 0.5650 - accuracy: 0.7689 - val_loss: 0.7615 - val_accuracy: 0.6819\n",
      "Epoch 9/50\n",
      "6368/6368 - 179s - loss: 0.5576 - accuracy: 0.7719 - val_loss: 0.7527 - val_accuracy: 0.6846\n",
      "Epoch 10/50\n",
      "6368/6368 - 179s - loss: 0.5518 - accuracy: 0.7745 - val_loss: 0.7807 - val_accuracy: 0.6784\n",
      "Epoch 11/50\n",
      "6368/6368 - 179s - loss: 0.5468 - accuracy: 0.7769 - val_loss: 0.7736 - val_accuracy: 0.6786\n",
      "Epoch 12/50\n",
      "6368/6368 - 180s - loss: 0.5426 - accuracy: 0.7782 - val_loss: 0.7789 - val_accuracy: 0.6810\n",
      "Epoch 13/50\n",
      "6368/6368 - 178s - loss: 0.5405 - accuracy: 0.7796 - val_loss: 0.7980 - val_accuracy: 0.6725\n",
      "Epoch 14/50\n",
      "6368/6368 - 179s - loss: 0.5363 - accuracy: 0.7811 - val_loss: 0.7898 - val_accuracy: 0.6750\n",
      "Epoch 15/50\n",
      "6368/6368 - 179s - loss: 0.5346 - accuracy: 0.7819 - val_loss: 0.8060 - val_accuracy: 0.6708\n",
      "Epoch 16/50\n",
      "6368/6368 - 179s - loss: 0.5330 - accuracy: 0.7822 - val_loss: 0.7875 - val_accuracy: 0.6738\n",
      "Epoch 17/50\n",
      "6368/6368 - 178s - loss: 0.5298 - accuracy: 0.7839 - val_loss: 0.7728 - val_accuracy: 0.6800\n",
      "Epoch 18/50\n",
      "6368/6368 - 178s - loss: 0.5275 - accuracy: 0.7847 - val_loss: 0.8015 - val_accuracy: 0.6741\n",
      "Epoch 19/50\n",
      "6368/6368 - 179s - loss: 0.5262 - accuracy: 0.7850 - val_loss: 0.8065 - val_accuracy: 0.6742\n",
      "Epoch 20/50\n",
      "6368/6368 - 179s - loss: 0.5247 - accuracy: 0.7858 - val_loss: 0.7993 - val_accuracy: 0.6730\n",
      "Epoch 21/50\n",
      "6368/6368 - 179s - loss: 0.5221 - accuracy: 0.7867 - val_loss: 0.8083 - val_accuracy: 0.6751\n",
      "Epoch 22/50\n",
      "6368/6368 - 179s - loss: 0.5205 - accuracy: 0.7877 - val_loss: 0.8285 - val_accuracy: 0.6642\n",
      "Epoch 23/50\n"
     ]
    }
   ],
   "source": [
    "split_train_val = int(np.floor(len(train_encoder_input) * 0.8))\n",
    "\n",
    "model_checkpoint_callback = tf.keras.callbacks.ModelCheckpoint(\n",
    "    filepath=checkpoint_filepath,\n",
    "    save_weights_only=True,\n",
    "    monitor='val_loss',\n",
    "    mode='auto',\n",
    "    save_best_only=True)\n",
    "\n",
    "model.fit([train_encoder_input[:split_train_val], train_decoder_input[:split_train_val]], \n",
    "          train_decoder_target[:split_train_val],\n",
    "          validation_data=([train_encoder_input[split_train_val:], train_decoder_input[split_train_val:]], \n",
    "          train_decoder_target[split_train_val:]),\n",
    "          epochs=epochs, batch_size=batch_size, verbose=2, callbacks=[model_checkpoint_callback])"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 9,
   "metadata": {},
   "outputs": [],
   "source": [
    "model.load_weights(checkpoint_filepath)\n",
    "pred = model.predict([test_encoder_input, test_decoder_input])"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 10,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Prediction horizon = 0\n",
      "accuracy_score = 0.8222921354756411\n",
      "classification_report =               precision    recall  f1-score   support\n",
      "\n",
      "           0     0.6910    0.5771    0.6289     21147\n",
      "           1     0.8593    0.9309    0.8937     98624\n",
      "           2     0.7136    0.5427    0.6165     19767\n",
      "\n",
      "    accuracy                         0.8223    139538\n",
      "   macro avg     0.7546    0.6835    0.7130    139538\n",
      "weighted avg     0.8131    0.8223    0.8143    139538\n",
      "\n",
      "-------------------------------\n",
      "Prediction horizon = 1\n",
      "accuracy_score = 0.7423497541888231\n",
      "classification_report =               precision    recall  f1-score   support\n",
      "\n",
      "           0     0.6292    0.5130    0.5652     27448\n",
      "           1     0.7868    0.8924    0.8363     86605\n",
      "           2     0.6454    0.4793    0.5501     25485\n",
      "\n",
      "    accuracy                         0.7423    139538\n",
      "   macro avg     0.6871    0.6283    0.6505    139538\n",
      "weighted avg     0.7300    0.7423    0.7307    139538\n",
      "\n",
      "-------------------------------\n",
      "Prediction horizon = 2\n",
      "accuracy_score = 0.7622869755908784\n",
      "classification_report =               precision    recall  f1-score   support\n",
      "\n",
      "           0     0.6883    0.6379    0.6622     31915\n",
      "           1     0.8027    0.8790    0.8391     78307\n",
      "           2     0.7097    0.5860    0.6420     29316\n",
      "\n",
      "    accuracy                         0.7623    139538\n",
      "   macro avg     0.7335    0.7010    0.7144    139538\n",
      "weighted avg     0.7570    0.7623    0.7572    139538\n",
      "\n",
      "-------------------------------\n",
      "Prediction horizon = 3\n",
      "accuracy_score = 0.7753228511229916\n",
      "classification_report =               precision    recall  f1-score   support\n",
      "\n",
      "           0     0.7129    0.7483    0.7302     38439\n",
      "           1     0.8441    0.8288    0.8364     66000\n",
      "           2     0.7190    0.7044    0.7116     35099\n",
      "\n",
      "    accuracy                         0.7753    139538\n",
      "   macro avg     0.7587    0.7605    0.7594    139538\n",
      "weighted avg     0.7765    0.7753    0.7757    139538\n",
      "\n",
      "-------------------------------\n",
      "Prediction horizon = 4\n",
      "accuracy_score = 0.783019679227164\n",
      "classification_report =               precision    recall  f1-score   support\n",
      "\n",
      "           0     0.7331    0.8229    0.7754     47952\n",
      "           1     0.8890    0.7626    0.8210     48050\n",
      "           2     0.7452    0.7616    0.7533     43536\n",
      "\n",
      "    accuracy                         0.7830    139538\n",
      "   macro avg     0.7891    0.7824    0.7832    139538\n",
      "weighted avg     0.7906    0.7830    0.7842    139538\n",
      "\n",
      "-------------------------------\n"
     ]
    }
   ],
   "source": [
    "evaluation_metrics(test_decoder_target, pred)\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": []
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": []
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": []
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": []
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": []
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Python 3",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.6.12"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 4
}
